数据库代码初步优化:实现抽象层repository
本节将前面分散在 AppModule 中的数据库代码进行模块化封装,创建 DatabaseModule 统一管理所有数据库连接,并实现 Repository 抽象层,为后续的业务开发提供清晰的数据访问接口。
模块化拆分:DatabaseModule
将所有数据库相关的代码从 AppModule 迁移到独立的模块结构中:
src/
database/
database.module.ts # 统一入口
database.constants.ts # 共享常量
typeorm/
typeorm-common.module.ts # TypeORM 配置
typeorm.provider.ts # 连接生命周期管理
typeorm.constants.ts # TypeORM 专用常量
prisma/
prisma-common.module.ts # Prisma 配置
mongoose/
mongoose-common.module.ts # Mongoose 配置
user/
user-abstract.repository.ts # 抽象 Repository
user.interface.ts # 用户适配器接口
repositories/
user-prisma.repository.ts
user-typeorm.repository.ts
user-mongoose.repository.ts
user.repository.ts # 统一 Repository(路由层)
text
创建 DatabaseModule
nest g module database/database --no-spec
bash
创建子模块
nest g module database/typeorm/typeorm-common --no-spec
nest g module database/prisma/prisma-common --no-spec
nest g module database/mongoose/mongoose-common --no-spec
bash
DatabaseModule 结构
@Module({
imports: [
TypeORMCommonModule,
PrismaCommonModule,
MongooseCommonModule,
],
exports: [
TypeORMCommonModule,
PrismaCommonModule,
MongooseCommonModule,
],
})
export class DatabaseModule {}
typescript
共享常量定义
// database.constants.ts
export const TYPEORM_DATABASE = 'TYPEORM_DATABASE';
export const PRISMA_DATABASE = 'PRISMA_DATABASE';
export const TYPEORM_CONNECTIONS = 'TYPEORM_CONNECTIONS';
typescript
迁移 TypeORM 代码
将 AppModule 中的 TypeORM 配置迁移到 TypeORMCommonModule:
// typeorm-common.module.ts
@Module({
imports: [
TypeOrmModule.forRootAsync({
// 使用 TypeORM 的异步配置
useClass: TypeORMConfigService,
}),
TypeOrmModule.forFeature([UserEntity]),
],
providers: [TypeORMProvider],
exports: [TypeOrmModule],
})
export class TypeORMCommonModule {}
typescript
连接生命周期管理迁移到 TypeORMProvider:
// typeorm.provider.ts
@Injectable()
export class TypeORMProvider implements OnModuleDestroy {
constructor(
@Inject(TYPEORM_CONNECTIONS)
private connections: Record<string, DataSource>,
) {}
onModuleDestroy() {
// 应用关闭时销毁所有 TypeORM 连接
Object.values(this.connections).forEach((ds) => {
if (ds && typeof ds.destroy === 'function') {
ds.destroy();
}
});
}
}
typescript
迁移 Prisma 和 Mongoose 代码
同样的方式处理 Prisma 和 Mongoose,将各自的 forRootAsync 配置和 Service 迁移到对应的 CommonModule 中。
迁移完成后的 AppModule 变得非常简洁:
@Module({
imports: [
DatabaseModule,
// 其他业务模块...
],
controllers: [AppController],
})
export class AppModule {}
typescript
Repository 抽象层实现
定义抽象类
// user-abstract.repository.ts
export abstract class UserAbstractRepository {
abstract find(): Promise<any[]>;
abstract create(userObj: any): Promise<any>;
abstract update(id: string, userObj: any): Promise<any>;
abstract delete(id: string): Promise<any>;
}
typescript
定义适配器接口
// user.interface.ts
export interface UserAdapter {
find(): Promise<any[]>;
create(userObj: any): Promise<any>;
update(id: string, userObj: any): Promise<any>;
delete(id: string): Promise<any>;
}
typescript
各 ORM 的具体实现
Prisma Repository:
export class UserPrismaRepository extends UserAbstractRepository {
constructor(@Inject('PRISMA_CLIENT') private client: PrismaClient) {
super();
}
find() {
return this.client.user.findMany();
}
create(userObj: any) {
return this.client.user.create({ data: userObj });
}
update(id: string, userObj: any) {
return this.client.user.update({ where: { id }, data: userObj });
}
delete(id: string) {
return this.client.user.delete({ where: { id } });
}
}
typescript
TypeORM Repository:
export class UserTypeORMRepository extends UserAbstractRepository {
constructor(
@InjectRepository(UserEntity)
private repo: Repository<UserEntity>,
) {
super();
}
find() {
return this.repo.find();
}
create(userObj: any) {
return this.repo.save(userObj);
}
update(id: string, userObj: any) {
return this.repo.update(id, userObj);
}
delete(id: string) {
return this.repo.delete(id);
}
}
typescript
Mongoose Repository:
export class UserMongooseRepository extends UserAbstractRepository {
constructor(@InjectModel('User') private userModel: Model<any>) {
super();
}
find() {
return this.userModel.find().exec();
}
create(userObj: any) {
return this.userModel.create(userObj);
}
update(id: string, userObj: any) {
return this.userModel.updateOne({ _id: id }, userObj);
}
delete(id: string) {
return this.userModel.deleteOne({ _id: id });
}
}
typescript
统一 UserRepository:路由层
export class UserRepository extends UserAbstractRepository {
constructor(
@Inject(REQUEST) private request: Request,
private mongooseRepo: UserMongooseRepository,
private typeormRepo: UserTypeORMRepository,
private prismaRepo: UserPrismaRepository,
) {
super();
}
private getRepository(): UserAdapter {
const { tenantId } = this.request.headers as any;
// 根据 tenantId 路由到对应的 Repository
switch (tenantId) {
case 'default': return this.mongooseRepo;
case 'default1': return this.typeormRepo;
case 'default2': return this.prismaRepo;
default: return this.prismaRepo;
}
}
find() {
return this.getRepository().find();
}
create(userObj: any) {
return this.getRepository().create(userObj);
}
update(id: string, userObj: any) {
return this.getRepository().update(id, userObj);
}
delete(id: string) {
return this.getRepository().delete(id);
}
}
typescript
Controller 中使用
迁移完成后,Controller 的代码变得极其简洁:
@Controller()
export class AppController {
constructor(private userRepository: UserRepository) {}
@Get()
findAll() {
return this.userRepository.find();
}
}
typescript
抽象类与接口的选择
| 方式 | 适用场景 | 复杂度 |
|---|---|---|
| 仅使用 Interface | 方法较少,不需要内部方法 | 简单 |
| 抽象类 + Interface | 方法多,需要区分公开/内部方法 | 中等 |
推荐做法:
- 初期项目只需创建
UserAdapter接口,直接在UserRepository中实现即可 - 当 Repository 方法增多且需要区分公开接口和内部实现时,再引入抽象类
架构要点总结
- DatabaseModule 统一管理 -- 所有数据库相关代码从 AppModule 迁入,保持 AppModule 简洁
- 子模块按 ORM 拆分 -- TypeORM、Prisma、Mongoose 各自独立,便于按需加载
- 连接管理集中在 Provider -- 连接销毁逻辑不再分散在多个 Service 中
- 三种 Repository 各自封装 ORM 差异 -- 调用者无需关心底层使用的是哪种 ORM
- UserRepository 作为路由层 -- 根据 tenantId 动态选择具体的 Repository 实现
- Controller 层极简 -- 只需注入
UserRepository,调用统一的 CRUD 方法
环境变量与按需加载
DatabaseModule 还可以通过环境变量控制加载哪些子模块:
@Module({
imports: [
// 根据环境变量决定加载哪些 ORM 模块
...(process.env.USE_TYPEORM ? [TypeORMCommonModule] : []),
...(process.env.USE_PRISMA ? [PrismaCommonModule] : []),
...(process.env.USE_MONGOOSE ? [MongooseCommonModule] : []),
],
})
export class DatabaseModule {}
typescript
导入模块会有性能消耗,未使用的模块不应该被加载。这种按需加载的方式在实际项目中非常实用。
↑